#pragma once
#include <vector>
#include "geometry.hpp"

struct draw_command {
	GLenum mode;
	GLint first;
	GLsizei count;
};

struct generated_data {
	GLfloat *vertices;
	std::vector<draw_command> drawlst;

	generated_data(GLfloat *vertices, std::vector<draw_command> drawlst) :
		vertices(vertices), 
		drawlst(drawlst) {

	}

	~generated_data() {
		delete vertices;
	}

	void
	draw() {
		std::vector<draw_command>::iterator iter = drawlst.begin();
		for (; iter != drawlst.end(); ++iter) {
			glDrawArrays(iter->mode, iter->first, iter->count);
		}
	}
};

class object_builder {
private:
	static const int FLOATS_PER_VERTEX = 3; // three components <x, y, z>

private:
	GLfloat *_vertices;
	std::vector<draw_command> _drawlst;
	int _offset;

	object_builder(int size_in_vertices) {
		_vertices = new GLfloat[size_in_vertices * FLOATS_PER_VERTEX];
		_offset = 0;
	}

private:
	void
	appendcircle(circle c, int numpoint) {
		int startvertex = _offset / FLOATS_PER_VERTEX;
		int numvertex = sizeof_circle_in_verties(numpoint);

		// the center point of the fan
		_vertices[_offset++] = c.center.x;
		_vertices[_offset++] = c.center.y;
		_vertices[_offset++] = c.center.z;

		// use <= to generate the point at starting angle twice to complete the FAN.
		// 0 and 2π are the same angle
		for (int i = 0; i <= numpoint; i++) {
			float radianangle = (float)i / (float)numpoint * (float)M_PI * 2.0f;
			_vertices[_offset++] = c.center.x + c.radius * cos(radianangle);
			_vertices[_offset++] = c.center.y;
			_vertices[_offset++] = c.center.z + c.radius * sin(radianangle);
		}

		draw_command command = {GL_TRIANGLE_FAN, startvertex, numvertex};
		_drawlst.push_back(command);
	}

	void append_opencylinder(cylinder c, int numpoint) {
		int startvertex = _offset / FLOATS_PER_VERTEX;
		int numvertex = sizeof_opencylinder_in_vertices(numpoint);
		float ystart = c.center.y - (float)c.height / 2.0f;
		float yend = c.center.y + (float)c.height / 2.0f;

		for (int i = 0; i <= numpoint; i++) {
			float radianangle = (float)i / (float)numpoint * M_PI * 2.0f;
			float x = c.center.x + c.radius * cos(radianangle);
			float z = c.center.z + c.radius * sin(radianangle);

			_vertices[_offset++] = x;
			_vertices[_offset++] = ystart;
			_vertices[_offset++] = z;

			_vertices[_offset++] = x;
			_vertices[_offset++] = yend;
			_vertices[_offset++] = z;
		}

		draw_command command = {GL_TRIANGLE_STRIP, startvertex, numvertex};
		_drawlst.push_back(command);
	}

	generated_data*
	build() {
		generated_data *gd = new generated_data(_vertices, _drawlst);
		return gd;
	}

public:
	static generated_data* 
	createpuck(cylinder puck, int numpoint) {
		int size = sizeof_circle_in_verties(numpoint) + sizeof_opencylinder_in_vertices(numpoint);
		logv("begin create puck, numpoint:%d size:%d", numpoint, size);
		object_builder ob(size);

		circle c(puck.center.translatey(puck.height / 2.0f), puck.radius);
		ob.appendcircle(c, numpoint);
		ob.append_opencylinder(puck, numpoint);

		return ob.build();
	}

	static generated_data*
	createmallet(point center, float radius, float height, int numpoint) {
		int size = (sizeof_circle_in_verties(numpoint) + sizeof_opencylinder_in_vertices(numpoint)) * 2;
		logv("begin create mallet, numpoint:%d, size:%d", numpoint, size);
		object_builder ob(size);

		float baseheight = height * 0.25f;
		circle basecircle(center.translatey(-baseheight), radius);
		cylinder basecylinder(basecircle.center.translatey(-baseheight / 2.0f), radius, baseheight);
		ob.appendcircle(basecircle, numpoint);
		ob.append_opencylinder(basecylinder, numpoint);

		// generate the handle
		float handleheight = height * 0.75f;
		float handleradius = radius / 3.0f;
		circle handlecircle(center.translatey(height * 0.5f), handleradius);
		cylinder handlecylinder(handlecircle.center.translatey(-handleheight / 2.0f), handleradius, handleheight);
		ob.appendcircle(handlecircle, numpoint);
		ob.append_opencylinder(handlecylinder, numpoint);

		return ob.build();
	}

private:
	// according the numpoint we want return the real vertex num needed to draw
	static int 
	sizeof_circle_in_verties(int numpoint) {
		return 1 + (numpoint + 1); // use GL_TRIANGLEFAN, plus the circle origin and the final point
	}

	// according the numpoint we want return the real vertex num needed to draw
	static int 
	sizeof_opencylinder_in_vertices(int numpoint) {
		return (numpoint + 1) * 2; // use GL_TRIANGLE_STRIP, for every point need two vertices and plus the final point
	}
};
